mod thread_util;
mod config;
mod option_util;
mod address;

use std::path::Path;

use log::debug;
use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use thread_util::set_num_threads;
use tokio::{runtime::Builder, sync::mpsc::{unbounded_channel, UnboundedReceiver}};

struct ConfigChanged;

fn start_notify_thread(config_paths: Vec<String>) -> (RecommendedWatcher,UnboundedReceiver<ConfigChanged>){
    let (tx,rx) = unbounded_channel();
    let mut watcher = notify::recommended_watcher(move |res: notify::Result<Event>| match res {
        Ok(event) =>{
            if matches!(event.kind,EventKind::Modify(..)) {
                tx.send(ConfigChanged{}).unwrap();
            }
        },
        Err(e) => println!("watch error: {:?}",e),
    }).unwrap();

    for config_path in config_paths {
        watcher.watch(Path::new(&config_path), RecursiveMode::NonRecursive)
        .unwrap();
    }

    (watcher,rx)
}

fn print_usage_and_exit(arg0: String){
    eprintln!("Usage: {} [--threads/-t N] <server uri or config filename> [server uri or config filename] [...]",arg0);
    std::process::exit(1);
}

fn main() {
    println!("Hello, world!");

    env_logger::init();
    let mut args: Vec<String> = std::env::args().collect();
    let arg0 = args.remove(0);
    let mut num_threads = 0usize;
    let mut dry_run = false;

    println!("{:?}",arg0);

    println!("{:?}",args);

    while !args.is_empty() && args[0].starts_with("-"){
        if args[0] == "--threads" || args[0] == "-t" {
            args.remove(0);
            if args.is_empty(){
                eprintln!("Missing threads argument.");
                print_usage_and_exit(arg0);
                return;
            }
            num_threads = match args.remove(0).parse::<usize>() {
                Ok(n) => n,
                Err(e) => {
                    eprintln!("Invalid thread count: {}",e);
                    print_usage_and_exit(arg0);
                    return;
                }
            };
        } else if args[0] == "--dry-run" || args[0] == "-d" {
            args.remove(0);
            dry_run = true;
        } else {
            eprintln!("Invalid argument: {}",args[0]);
            print_usage_and_exit(arg0);
            return;
        }
    }

    if args.is_empty() {
        println!("No config specified, assuming loading from file config.shoes.yaml");
        args.push("config.shoes.yaml".to_string());
    }

    if dry_run {
        println!("Starting dry run.");
    }

    if num_threads ==0 {
        num_threads = std::cmp::max(2,
             std::thread::available_parallelism()
             .map(|n| n.get())
             .unwrap_or(1)
        );
        debug!("Runtime threads: {}",num_threads);
    }else {
        println!("Using custom thread count ({})",num_threads);
    }

    set_num_threads(num_threads);

    let mut builder = if num_threads == 1 {
        Builder::new_current_thread()
    }else{
        let mut mt = Builder::new_multi_thread();
        mt.worker_threads(num_threads);
        mt
    };

    let runtime = builder
        .enable_io()
        .enable_time()
        .build()
        .expect("Could not build tokio runtime");

    runtime.block_on(async move {
        let (_watcher,mut config_rx) = start_notify_thread(args.clone());
        loop{
           let configs = match config::load_configs(&args).await {
               Ok(c) => c,
               Err(e) => {
                eprintln!("Failed to load server configs: {}\n",e);
                print_usage_and_exit(arg0);
                return;
               }
           };

           for config in configs.iter() {
            debug!("====================================================");
            debug!("{:#?}",config);
           }
           debug!("====================================================");
        }
    });
}
